package com.smartboard.network.server;

import com.smartboard.network.game.AbstractGame;
import com.smartboard.network.game.GameObject;
import com.smartboard.network.game.IPlayGame;
import com.smartboard.network.game.IRoom;
import com.smartboard.network.game.IUser;
import com.smartboard.network.socket.SocketHost;

import java.io.Serializable;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Created by cheas on 2018/8/16.
 */

public class GameServer extends AbstractGame implements SocketHost.OnClientDisconnectListener {
    private static final Logger LOG = LoggerFactory.getLogger(GameServer.class);

    private SocketHost mSocket;
    private int mChessType;

    private List<IRoom> mRooms = new ArrayList<>();
    // gameId, moves
    private Map<Integer, List<String> > mGameMoves = new HashMap<>();
    // name, IUser
    private Map<String, IUser> mAllNameUsers = new HashMap<>();

    private Map<IUser, Socket> mAllSocketUsers = new HashMap<>();

    private static GameServer sGameServer;

    public GameServer inst() {
        return sGameServer;
    }

    public GameServer(int chessType) {
        mSocket = new SocketHost(getPort(chessType));
        mSocket.setOnReceiveListener(this);
        mSocket.open();
        this.mChessType = chessType;
        sGameServer = this;
        new HeartBeatThread().start();
        mSocket.setOnClientDisconnectListener(this);
    }

    protected void response(Socket socket, int cmd, Serializable content) {
        GameObject gameData = new GameObject();
        gameData.cmd = cmd;
        gameData.type = CMD_TYPE_RESP;
        gameData.content = content;
        mSocket.send(socket, gameData);
    }

    private IRoom findRoom(String name) {
        if(name == null || name.isEmpty())
            return null;

        for(IRoom room : mRooms) {
            if(room.getName().equals(name))
                return room;
        }
        return null;
    }

    private IRoom findRoom(int id) {
        Object obj = IdsPool.getObject(id);
        if(obj != null && obj instanceof IRoom)
            return (IRoom)obj;
        return null;
    }

    private void broadcastRoomNumChanged() {
        GameObject gameData = new GameObject();
        gameData.cmd = CMD_ROOM_NUM_CHANGED;
        gameData.type = CMD_TYPE_SEND;

        RoomNumChangedData data = new RoomNumChangedData(mRooms);
        gameData.content = data;
        mSocket.sendToAll(gameData);
    }

    private void sendRoomNumChanged(IUser user) {
        GameObject gameData = new GameObject();
        gameData.cmd = CMD_ROOM_NUM_CHANGED;
        gameData.type = CMD_TYPE_SEND;

        RoomNumChangedData data = new RoomNumChangedData(mRooms);
        gameData.content = data;
        mSocket.send(mAllSocketUsers.get(user), gameData);
    }

    private void broadcastGameNumChanged(IRoom room) {
        GameObject gameData = new GameObject();
        gameData.cmd = CMD_GAME_NUM_CHANGED;
        gameData.type = CMD_TYPE_SEND;

        GameNumChangedData data = new GameNumChangedData(((Room)room).clone());
        gameData.content = data;
        for(IUser user : room.getUsers()) {
            Socket socket = mAllSocketUsers.get(user);
            mSocket.send(socket, gameData);
        }
    }

    private void broadcastRoomUserChanged(IRoom room, IUser user, boolean leave) {
        GameObject gameData = new GameObject();
        gameData.cmd = CMD_ROOM_USER_CHANGED;
        gameData.type = CMD_TYPE_SEND;

        RoomUserChangedData data = new RoomUserChangedData(((Room)room).clone(), ((User)user).clone(), leave);
        gameData.content = data;
        for(IUser user0 : room.getUsers()) {
            Socket socket = mAllSocketUsers.get(user0);
            mSocket.send(socket, gameData);
        }
    }

    private void broadcastGameDataCopy(GameObject gameData, Serializable content) {
        GameObject obj = new GameObject();
        obj.cmd = gameData.cmd;
        obj.type = gameData.type;
        obj.content = content;
        for(IUser user0 : mAllNameUsers.values()) {
            mSocket.send(mAllSocketUsers.get(user0), obj);
        }
    }

    private void broadcastGameDataCopy(IPlayGame game, GameObject gameData, Serializable content) {
        GameObject obj = new GameObject();
        obj.type = gameData.type;
        obj.cmd = gameData.cmd;
        obj.content = content;
        broadcastGameData(game, obj);
    }

    private void broadcastGameData(IPlayGame game, GameObject gameData) {
        for(IUser user0 : game.getWatchers()) {
            Socket socket = mAllSocketUsers.get(user0);
            mSocket.send(socket, gameData);
        }

        if(game.getPlayer1() != null)
            mSocket.send(mAllSocketUsers.get(game.getPlayer1()), gameData);
        if(game.getPlayer2() != null)
            mSocket.send(mAllSocketUsers.get(game.getPlayer2()), gameData);
    }

    private void broadcastGameUserChanged(IPlayGame game, IUser user, boolean leave) {
        GameObject gameData = new GameObject();
        gameData.cmd = CMD_GAME_USER_CHANGED;
        gameData.type = CMD_TYPE_SEND;

        GameUserChangedData data = new GameUserChangedData(((PlayGame)game).clone(), ((User)user).clone(), leave);
        gameData.content = data;
        broadcastGameData(game, gameData);
    }

    private void broadcastGameStateChanged(IPlayGame game) {
        GameObject gameData = new GameObject();
        gameData.cmd = CMD_GAME_STATE_CHANGED;
        gameData.type = CMD_TYPE_SEND;

        GameStateChangedData data = new GameStateChangedData(((PlayGame)game).clone());
        gameData.content = data;
        broadcastGameData(game, gameData);
    }

    private void broadcastUserInfoChanged(IUser user) {
        GameObject gameData = new GameObject();
        gameData.cmd = CMD_USER_INFO_CHANGED;
        gameData.type = CMD_TYPE_SEND;

        UserInfoChangedData data = new UserInfoChangedData(((User)user).clone());
        gameData.content = data;
        for(IUser user0 : mAllNameUsers.values()) {
            mSocket.send(mAllSocketUsers.get(user0), gameData);
        }
    }

    @Override
    protected void handleReceiveData(Socket socket, GameObject gameData) {
        LOG.debug("handleReceiveData() cmd:" + gameData.cmd);
        if(gameData.type == CMD_TYPE_RESP) {
            LOG.debug("should not receive resp from client!!");
            return;
        }

        switch (gameData.cmd) {
            case CMD_LOGIN: {
                LoginData loginData = (LoginData)gameData.content;
                IUser user = mAllNameUsers.get(loginData.name);
                LOG.debug("user login:" + loginData.name + ", exist:" + (user != null));
                
                RespDataBase respData = new RespDataBase(null);
                
                IUser retUser = null;
                if(user == null) {
                    retUser = new User(loginData.name, loginData.chessType);
                    respData.user = retUser;
                    mAllNameUsers.put(loginData.name, retUser);
                    mAllSocketUsers.put(retUser, socket);
                } else {
                    respData.errorCode = ERROR_NAME_EXIST;
                }
                response(socket, CMD_LOGIN, respData);
                if(retUser != null) {
                    sendRoomNumChanged(retUser);
                    broadcastGameDataCopy(gameData, respData);
                }
                break;
            }
            case CMD_LOGOUT: {
                LogoutData logoutData = (LogoutData)gameData.content;
                RespDataBase respData = new RespDataBase(null);

                Object obj = IdsPool.getObject(logoutData.userId);
                if(obj != null && obj instanceof IUser) {
                    IUser user = (IUser)obj;
                    LOG.debug("user logout:" + user.getName());
                    IdsPool.removeObject(logoutData.userId);
                    mAllNameUsers.remove(user.getName());
                    mAllSocketUsers.remove(user);
                    respData.user = user;
                    response(socket, CMD_LOGOUT, respData);
                    broadcastGameDataCopy(gameData, respData);
                    
                    if(user.getRoomId() != 0)
                        broadcastRoomUserChanged((IRoom)IdsPool.getObject(user.getRoomId()), user, true);
                    if(user.getGameId() != 0)
                        broadcastGameUserChanged((IPlayGame)IdsPool.getObject(user.getGameId()), user, true);
                } else {
                    LOG.debug("logout error, user not login:" + logoutData.userId);
                    respData.errorCode = ERROR_NOT_LOGIN;
                    response(socket, CMD_LOGOUT, respData);
                }
                break;
            }
            case CMD_CREATE_ROOM: {
                CreateRoomData createRoomData = (CreateRoomData)gameData.content;
                RespCreateRoomData respData = new RespCreateRoomData(null, null);
                if(findRoom(createRoomData.roomName) != null) {
                    LOG.debug("create room error, room exist:" + createRoomData.roomName);
                    respData.errorCode = ERROR_NAME_EXIST;
                    response(socket, CMD_CREATE_ROOM, respData);
                } else {
                    Object obj = IdsPool.getObject(createRoomData.userId);
                    LOG.debug("create room:" + createRoomData.roomName);
                    if(obj != null && obj instanceof IUser) {
                        IUser user = (IUser)obj;
                        Room room = new Room(createRoomData.roomName, user, createRoomData.password);
                        room.enterRoom(user);
                        respData.room = room;
                        respData.user = user;
                        mRooms.add(room);
                        response(socket, CMD_CREATE_ROOM, respData);
                        broadcastRoomNumChanged();
                    } else {
                        LOG.debug("create room error, user not login:" + createRoomData.userId);
                        respData.errorCode = ERROR_NOT_LOGIN;
                        response(socket, CMD_CREATE_ROOM, respData);
                    }
                }
                break;
            }
            case CMD_ENTER_ROOM: {
                EnterRoomData enterRoomData = (EnterRoomData)gameData.content;
                RespDataBase respData = new RespDataBase(null);

                Object obj = IdsPool.getObject(enterRoomData.userId);
                if(obj != null && obj instanceof IUser) {
                    IUser user = (IUser)obj;
                    Room room = (Room)IdsPool.getObject(enterRoomData.roomId);
                    if(room.needPassword() && (enterRoomData.password == null
                            || enterRoomData.password.isEmpty()
                            || !enterRoomData.password.equals(room.getPassword()))) {
                        LOG.debug("enter room error, password incorrect:" + enterRoomData.userId);
                        respData.errorCode = ERROR_INVALID_PASSWORD;
                        response(socket, CMD_ENTER_ROOM, respData);
                        break;
                    }
                    LOG.debug("enter room:" + user.getName());
                    room.enterRoom(user);
                    respData.user = user;
                    response(socket, CMD_ENTER_ROOM, respData);
                    broadcastRoomUserChanged(room, user, false);
                } else {
                    LOG.debug("enter room error, user not login:" + enterRoomData.userId);
                    respData.errorCode = ERROR_NOT_LOGIN;
                    response(socket, CMD_ENTER_ROOM, respData);
                }
                break;
            }
            case CMD_EXIT_ROOM: {
                ExitRoomData exitRoomData = (ExitRoomData)gameData.content;
                RespDataBase respData = new RespDataBase(null);

                Object obj = IdsPool.getObject(exitRoomData.userId);
                if(obj != null && obj instanceof IUser) {
                    IUser user = (IUser)obj;
                    LOG.debug("exit room:" + user.getName());
                    Room room = (Room)IdsPool.getObject(exitRoomData.roomId);
                    respData.errorCode = room.exitRoom(user);
                    respData.user = user;
                    response(socket, CMD_EXIT_ROOM, respData);
                    if(respData.errorCode != ERROR_NONE)
                        broadcastRoomUserChanged(room, user, true);
                } else {
                    LOG.debug("exit room error, user not login:" + exitRoomData.userId);
                    respData.errorCode = ERROR_NOT_LOGIN;
                    response(socket, CMD_EXIT_ROOM, respData);
                }
                break;
            }
            case CMD_CREATE_GAME: {
                CreateGameData createGameData = (CreateGameData)gameData.content;
                RespCreateGameData respData = new RespCreateGameData(null, null);
                IRoom room = findRoom(createGameData.roomId);
                if(room == null) {
                    LOG.debug("create game error, room not exist:" + createGameData.roomId);
                    respData.errorCode = ERROR_ROOM_NOT_EXIST;
                    response(socket, CMD_CREATE_GAME, respData);
                } else {
                    Object obj = IdsPool.getObject(createGameData.userId);
                    if(obj != null && obj instanceof IUser) {
                        IUser user = (IUser)obj;
                        respData.user = user;
                        if(!room.isUserInRoom(user)) {
                            LOG.debug("create game, user not in room:" + user.getName());
                            respData.errorCode = ERROR_USER_NOT_IN_ROOM;
                            response(socket, CMD_CREATE_GAME, respData);
                        } else {
                            List<IPlayGame> games = new ArrayList<>();
                            for(int i = 0; i < createGameData.num; i++) {
                                games.add(((Room)room).createGame(user, createGameData.gameInfo));
                            }
                            LOG.debug("create game:" + user.getName() + ", num:" + createGameData.num);
                            respData.games = games;
                            response(socket, CMD_CREATE_GAME, respData);
                            broadcastGameNumChanged(room);
                        }
                    } else {
                        LOG.debug("create game error, user not login:" + createGameData.userId);
                        respData.errorCode = ERROR_NOT_LOGIN;
                        response(socket, CMD_CREATE_GAME, respData);
                    }
                }
                break;
            }
            case CMD_JOIN_GAME: {
                JoinGameData joinGameData = (JoinGameData)gameData.content;
                RespDataBase respData = new RespDataBase(null);

                Object obj = IdsPool.getObject(joinGameData.userId);
                if(obj != null && obj instanceof IUser) {
                    IUser user = (IUser)obj;
                    respData.user = user;
                    LOG.debug("join game:" + user.getName());
                    PlayGame game = (PlayGame)IdsPool.getObject(joinGameData.gameId);
                    game.joinGame(user);
                    response(socket, CMD_JOIN_GAME, respData);
                    broadcastGameUserChanged(game, user, false);
                } else {
                    LOG.debug("join game error, user not login:" + joinGameData.userId);
                    respData.errorCode = ERROR_NOT_LOGIN;
                    response(socket, CMD_JOIN_GAME, respData);
                }
                break;
            }
            case CMD_QUIT_GAME: {
                QuitGameData quitGameData = (QuitGameData)gameData.content;
                RespDataBase respData = new RespDataBase(null);

                Object obj = IdsPool.getObject(quitGameData.userId);
                if(obj != null && obj instanceof IUser) {
                    IUser user = (IUser)obj;
                    respData.user = user;
                    LOG.debug("quit game:" + user.getName());
                    PlayGame game = (PlayGame) IdsPool.getObject(quitGameData.gameId);
                    boolean escape = game.quitGame(user);
                    response(socket, CMD_QUIT_GAME, respData);
                    broadcastGameUserChanged(game, user, true);
                    if(escape) {
                        game.setGameState(IPlayGame.STATE_OVER);
                        game.setResult(IPlayGame.RESULT_WIN_LOST);
                        game.setWinner((user == game.getPlayer1()) ? game.getPlayer2() : game.getPlayer1());
                        broadcastGameStateChanged(game);
                    }
                } else {
                    LOG.debug("quit game error, user not login:" + quitGameData.userId);
                    respData.errorCode = ERROR_NOT_LOGIN;
                    response(socket, CMD_QUIT_GAME, respData);
                }
                break;
            }
            case CMD_PLAY_MOVE: {
                PlayMoveData playMoveData = (PlayMoveData)gameData.content;
                RespPlayMoveData respData = new RespPlayMoveData(null, null, playMoveData.move);

                Object obj = IdsPool.getObject(playMoveData.userId);
                if(obj != null && obj instanceof IUser) {
                    IUser user = (IUser)obj;
                    PlayGame game = (PlayGame) IdsPool.getObject(playMoveData.gameId);
                    respData.user = user;
                    respData.game = game;
                    if(user != game.getNextPlayer())
                        LOG.debug("play move:" + user.getName());
                    List<String> moves = mGameMoves.get(game.getId());
                    if(moves == null) {
                        moves = new ArrayList<>();
                        mGameMoves.put(game.getId(), moves);
                    }
                    moves.add(playMoveData.move);
                    response(socket, CMD_PLAY_MOVE, respData);
                    broadcastGameDataCopy(game, gameData, respData);
                } else {
                    LOG.debug("play move error, user not login:" + playMoveData.userId);
                    respData.errorCode = ERROR_NOT_LOGIN;
                    response(socket, CMD_PLAY_MOVE, respData);
                }
                break;
            }
            case CMD_UNDO: {
                UndoData undoData = (UndoData)gameData.content;
                RespGameData respData = new RespGameData(null, null);

                Object obj = IdsPool.getObject(undoData.userId);
                if(obj != null && obj instanceof IUser) {
                    IUser user = (IUser)obj;
                    PlayGame game = (PlayGame) IdsPool.getObject(undoData.gameId);
                    respData.user = user;
                    respData.game = game;
                    IUser opponent = (user == game.getPlayer1()) ? game.getPlayer2() : game.getPlayer1();

                    if(undoData.code == USER_REQUEST) {
                        LOG.debug("undo move request:" + user.getName());
                        if(user.equals(game.getNextPlayer())) {
                            // 当前正在思考的棋手不能undo
                            LOG.debug("undo move request:" + user.getName());
                            respData.errorCode = ERROR_UNDO_DISABLE;
                        }
                        response(socket, CMD_UNDO, respData);

                        if(respData.errorCode != ERROR_NONE)
                            mSocket.send(mAllSocketUsers.get(opponent), gameData);
                    } else if(undoData.resp == RESP_REJECT) {
                        response(socket, CMD_UNDO, respData);
                        LOG.debug("undo move reject:" + user.getName());
                        mSocket.send(mAllSocketUsers.get(opponent), gameData);
                    } else {
                        response(socket, CMD_UNDO, respData);
                        LOG.debug("undo move:" + user.getName());
                        respData.user = opponent;   // undo请求是oppnent发出的，user是同意
                        List<String> moves = mGameMoves.get(game.getId());
                        if(moves != null && moves.size() > 0) {
                            moves.remove(moves.size()-1);
                            game.changePlayer();
                            broadcastGameDataCopy(game, gameData, respData);
                        }
                    }
                } else {
                    LOG.debug("undo move error, user not login:" + undoData.userId);
                    respData.errorCode = ERROR_NOT_LOGIN;
                    response(socket, CMD_UNDO, respData);
                }
                break;
            }
            case CMD_RESIGN: {
                ResignData resignData = (ResignData)gameData.content;
                RespGameData respData = new RespGameData(null, null);

                Object obj = IdsPool.getObject(resignData.userId);
                if(obj != null && obj instanceof IUser) {
                    IUser user = (IUser)obj;
                    LOG.debug("resign:" + user.getName());
                    PlayGame game = (PlayGame) IdsPool.getObject(resignData.gameId);
                    respData.user = user;
                    respData.game = game;
                    response(socket, CMD_RESIGN, respData);
                    broadcastGameDataCopy(game, gameData, respData);

                    game.setResult(IPlayGame.RESULT_WIN_LOST);
                    game.setWinner((user == game.getPlayer1()) ? game.getPlayer2() : game.getPlayer1());
                    game.setGameState(IPlayGame.STATE_OVER);
                    broadcastGameStateChanged(game);
                } else {
                    LOG.debug("resign error, user not login:" + resignData.userId);
                    respData.errorCode = ERROR_NOT_LOGIN;
                    response(socket, CMD_RESIGN, respData);
                }
                break;
            }
            case CMD_DRAW: {
                DrawData drawData = (DrawData)gameData.content;
                RespGameData respData = new RespGameData(null, null);

                Object obj = IdsPool.getObject(drawData.userId);
                if(obj != null && obj instanceof IUser) {
                    IUser user = (IUser)obj;
                    PlayGame game = (PlayGame) IdsPool.getObject(drawData.gameId);
                    respData.user = user;
                    respData.game = game;
                    IUser opponent = (user == game.getPlayer1()) ? game.getPlayer2() : game.getPlayer1();
                    response(socket, CMD_DRAW, respData);
                    if(drawData.code == USER_REQUEST) {
                        LOG.debug("draw request:" + user.getName());
                        game.setResult(IPlayGame.RESULT_DRAW);
                        mSocket.send(mAllSocketUsers.get(opponent), gameData);
                    } else if(drawData.resp == RESP_REJECT) {
                        LOG.debug("draw reject:" + user.getName());
                        mSocket.send(mAllSocketUsers.get(opponent), gameData);
                    } else {
                        LOG.debug("draw:" + user.getName());
                        game.setGameState(IPlayGame.STATE_OVER);
                        respData.user = opponent;   // draw请求是oppnent发出的，user是同意
                        broadcastGameDataCopy(game, gameData, respData);
                        broadcastGameStateChanged(game);
                    }
                } else {
                    LOG.debug("draw error, user not login:" + drawData.userId);
                    respData.errorCode = ERROR_NOT_LOGIN;
                    response(socket, CMD_DRAW, respData);
                }
                break;
            }
            case CMD_GET_MOVES: {
                GetMovesData getMovesData = (GetMovesData)gameData.content;
                RespGetMovesData respData = new RespGetMovesData(null, null, null);

                Object obj = IdsPool.getObject(getMovesData.userId);
                if(obj != null && obj instanceof IUser) {
                    IUser user = (IUser)obj;
                    PlayGame game = (PlayGame) IdsPool.getObject(getMovesData.gameId);
                    respData.user = user;
                    respData.game = game;
                    List<String> moves = mGameMoves.get(game.getId());
                    if(moves == null) {
                        moves = new ArrayList<>();
                        mGameMoves.put(game.getId(), moves);
                    }
                    respData.moves = moves;
                } else {
                    LOG.debug("get moves error, user not login:" + getMovesData.userId);
                    respData.errorCode = ERROR_NOT_LOGIN;
                }
                response(socket, CMD_GET_MOVES, respData);
                break;
            }
            case CMD_READY: {
                ReadyData readyData = (ReadyData)gameData.content;
                RespGameData respData = new RespGameData(null, null);

                Object obj = IdsPool.getObject(readyData.userId);
                if(obj != null && obj instanceof IUser) {
                    IUser user = (IUser)obj;
                    LOG.debug("user ready:" + user.getName());
                    PlayGame game = (PlayGame) IdsPool.getObject(readyData.gameId);
                    respData.user = user;
                    respData.game = game;
                    response(socket, CMD_READY, respData);
                    broadcastGameDataCopy(game, gameData, respData);

                    ((User)user).setIsReady(true);
                    if(game.getPlayer1() != null && game.getPlayer2() != null) {
                        if(((User)game.getPlayer1()).isReady() && ((User)game.getPlayer2()).isReady()) {
                            game.setGameState(IPlayGame.STATE_PLAYING);
                            boolean changeSide = new Random().nextBoolean();
                            game.initNextPlayer(changeSide);
                            broadcastGameStateChanged(game);
                        }
                    }
                } else {
                    LOG.debug("user ready error, user not login:" + readyData.userId);
                    respData.errorCode = ERROR_NOT_LOGIN;
                    response(socket, CMD_READY, respData);
                }
                break;
            }
            case CMD_GAME_START: {
                GameStartData gameStartData = (GameStartData)gameData.content;
                RespGameData respData = new RespGameData(null, null);

                Object obj = IdsPool.getObject(gameStartData.userId);
                if(obj != null && obj instanceof IUser) {
                    IUser user = (IUser)obj;
                    LOG.debug("game start:" + user.getName());
                    PlayGame game = (PlayGame) IdsPool.getObject(gameStartData.gameId);
                    respData.user = user;
                    respData.game = game;
                    if(game.getGameState() != IPlayGame.STATE_PLAYING) {
                        game.setGameState(IPlayGame.STATE_PLAYING);
                        boolean changeSide = new Random().nextBoolean();
                        game.initNextPlayer(changeSide);
                        response(socket, CMD_GAME_START, respData);
                        broadcastGameDataCopy(game, gameData, respData);
                        broadcastGameStateChanged(game);
                    }
                } else {
                    LOG.debug("game start error, user not login:" + gameStartData.userId);
                    respData.errorCode = ERROR_NOT_LOGIN;
                    response(socket, CMD_GAME_START, respData);
                }
                break;
            }
            case CMD_GAME_OVER: {
                GameOverData gameOverData = (GameOverData)gameData.content;
                RespGameData respData = new RespGameData(null, null);

                Object obj = IdsPool.getObject(gameOverData.userId);
                if(obj != null && obj instanceof IUser) {
                    IUser user = (IUser)obj;
                    LOG.debug("game over:" + user.getName());
                    PlayGame game = (PlayGame) IdsPool.getObject(gameOverData.gameId);
                    respData.user = user;
                    respData.game = game;
                    if(game.getGameState() != IPlayGame.STATE_OVER) {
                        game.setGameState(IPlayGame.STATE_OVER);
                        response(socket, CMD_GAME_OVER, respData);
                        broadcastGameDataCopy(game, gameData, respData);
                    }
                } else {
                    LOG.debug("game over error, user not login:" + gameOverData.userId);
                    respData.errorCode = ERROR_NOT_LOGIN;
                    response(socket, CMD_GAME_OVER, respData);
                }
                break;
            }
            case CMD_SEND_MESSAGE: {
                SendMessageData sendMessageData = (SendMessageData)gameData.content;
                RespMessageData respData = new RespMessageData(null, null,
                        null, null, sendMessageData.msg);

                Object obj = IdsPool.getObject(sendMessageData.userId);
                if(obj != null && obj instanceof IUser) {
                    IUser user = (IUser)obj;
                    respData.user = user;
                    LOG.debug("send message:" + user.getName() + ", msg:" + sendMessageData.msg);
                    response(socket, CMD_SEND_MESSAGE, respData);
                    if(sendMessageData.toUserId != 0) {
                        Object obj1 = IdsPool.getObject(sendMessageData.toUserId);
                        if(obj1 != null && obj1 instanceof IUser) {
                            respData.toUser = (IUser)obj1;
                            mSocket.send(mAllSocketUsers.get(obj1), respData);
                        } else {
                            LOG.debug("send message error, to user not login:" + sendMessageData.toUserId);
                        }
                    } else if(sendMessageData.gameId != 0) {
                        PlayGame game = (PlayGame) IdsPool.getObject(sendMessageData.gameId);
                        respData.game = game;
                        broadcastGameDataCopy(game, gameData, respData);
                    } else {
                        assert(sendMessageData.roomId != 0);
                        Room room = (Room)IdsPool.getObject(sendMessageData.roomId);
                        respData.room = room;
                        for(IPlayGame game : room.getGames()) {
                            broadcastGameDataCopy(game, gameData, respData);
                        }
                    }
                } else {
                    LOG.debug("send message error, user not login:" + sendMessageData.userId);
                    respData.errorCode = ERROR_NOT_LOGIN;
                    response(socket, CMD_SEND_MESSAGE, respData);
                }
                break;
            }
            case CMD_SET_NICKNAME: {
                SetNickNameData setNickNameData = (SetNickNameData)gameData.content;
                RespDataBase respData = new RespDataBase(null);

                Object obj = IdsPool.getObject(setNickNameData.userId);
                if(obj != null && obj instanceof IUser) {
                    User user = (User)obj;
                    respData.user = user;
                    LOG.debug("set nick name:" + user.getName());
                    user.setNickName(setNickNameData.nickName);
                    response(socket, CMD_SET_NICKNAME, respData);
                    broadcastUserInfoChanged(user);
                } else {
                    LOG.debug("set nick name error, user not login:" + setNickNameData.userId);
                    respData.errorCode = ERROR_NOT_LOGIN;
                    response(socket, CMD_SET_NICKNAME, respData);
                }
                break;
            }
            case CMD_SET_AVANTAR: {
                SetAvantarData setAvantarData = (SetAvantarData)gameData.content;
                RespDataBase respData = new RespDataBase(null);

                Object obj = IdsPool.getObject(setAvantarData.userId);
                if(obj != null && obj instanceof IUser) {
                    User user = (User)obj;
                    respData.user = user;
                    LOG.debug("set avantar:" + user.getName());
                    response(socket, CMD_SET_AVANTAR, respData);
                    // TODO implement here
                    //broadcastUserInfoChanged((IUser)obj);
                } else {
                    LOG.debug("set avantar error, user not login:" + setAvantarData.userId);
                    respData.errorCode = ERROR_NOT_LOGIN;
                    response(socket, CMD_SET_AVANTAR, respData);
                }
                break;
            }
            case CMD_SET_ROOM_NAME: {
                SetRoomNameData setRoomNameData = (SetRoomNameData)gameData.content;
                RespDataBase respData = new RespDataBase(null);

                Object obj = IdsPool.getObject(setRoomNameData.userId);
                if(obj != null && obj instanceof IUser) {
                    User user = (User)obj;
                    respData.user = user;
                    LOG.debug("set room name:" + user.getName());
                    response(socket, CMD_SET_ROOM_NAME, respData);

                    Room room = (Room)IdsPool.getObject(setRoomNameData.roomId);
                    room.setName(setRoomNameData.name);
                    broadcastRoomNumChanged();
                } else {
                    LOG.debug("set room error, user not login:" + setRoomNameData.userId);
                    respData.errorCode = ERROR_NOT_LOGIN;
                    response(socket, CMD_SET_ROOM_NAME, respData);
                }
                break;
            }
            case CMD_GET_INFO: {
                GetInfoData getInfoData = (GetInfoData)gameData.content;
                RespGetInfoData respData = new RespGetInfoData(null, getInfoData.id, null);
                Object obj = IdsPool.getObject(getInfoData.userId);
                if(obj != null && obj instanceof IUser) {
                    respData.user = (IUser)obj;
                    respData.obj = IdsPool.getObject(getInfoData.id);
                    if(respData.obj == null)
                        LOG.debug("get info error, entity not exist:" + getInfoData.id);
                    else
                        LOG.debug("get info:" + respData.obj);
                } else {
                    LOG.debug("get info error, user not login:" + getInfoData.userId);
                    respData.errorCode = ERROR_NOT_LOGIN;
                }
                response(socket, CMD_GET_INFO, respData);
                break;
            }
            default: {
                LOG.debug("cmd not handled:" + gameData.cmd);
                break;
            }
        }
    }
    
    private class HeartBeatThread extends Thread {
        @Override
        public void run() {
            while(mSocket != null) {
                try {
                    GameObject obj = new GameObject();
                    obj.cmd = CMD_HEART_BEAT;
                    obj.type = CMD_TYPE_SEND;
                    obj.content = null;
                    mSocket.sendToAll(obj);
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Override
    public void onClientDisconnect(Socket socket) {
        Collection<IUser> users = mAllSocketUsers.keySet();
        for(IUser user : users) {
            Socket s = mAllSocketUsers.get(user);
            if(s.equals(socket)) {
                mAllSocketUsers.remove(user);
                mAllNameUsers.remove(user.getName());
                
                // broadcast to other users
                GameObject obj = new GameObject();
                obj.cmd = CMD_OFFLINE;
                obj.type = CMD_TYPE_SEND;
                obj.content = null;
                for(IUser user0 : mAllNameUsers.values()) {
                    mSocket.send(mAllSocketUsers.get(user0), obj);
                }
                if(user.getRoomId() != 0)
                    broadcastRoomUserChanged((IRoom)IdsPool.getObject(user.getRoomId()), user, true);
                if(user.getGameId() != 0)
                    broadcastGameUserChanged((IPlayGame)IdsPool.getObject(user.getGameId()), user, true);
                break;
            }
        }
    }
}
